package com.netease.nieweb.emarket.lottery.service;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.netease.nieweb.emarket.entity.LotteryConfig;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 　* @Description: TODO
 * 　* @author qiyu
 * 　* @date 2020/12/15 14:54
 *
 */
public abstract class AbstractLotteryService implements  ApplicationContextAware {

    /**
     * （活动id，奖品概率列表），记录奖品的概率随机区间
     */
    protected static Map<String, List<Integer>> prizeProbability=new HashMap<>();

    /**
     * 活动id，库存数据列表），记录奖品的库存数量
     */
    protected static Map<String, List<Integer>> stocks=new HashMap<>();

    /**
     * 活动id，奖品id列表），记录奖品id
     */
    protected static Map<String, List<String>> prize=new HashMap<>();

    @Autowired
    protected StringRedisTemplate stringRedisTemplate;


    /**
     * （活动id，库存key值），key值为活动在redis中的hash表的key值
     */
    protected static Map<String, String> LOTTERY_KEY=new HashMap<>();

    /**
     * （活动id，兜底的最小随机值）用于兜底数据的随机
     */
    protected static Map<String, Integer> thankMin=new HashMap<>();

    /**
     * （活动id，最大随机值）记录抽奖时的最大随机值
     */
    protected static Map<String, Integer> sum=new HashMap<>();

    /**
     * 活动列表
     */
    protected static List<String> activityIdList=new ArrayList<>();

    /**
     * 奖品配置表
     */
    protected static Map<String, LotteryConfig> lotteryConfigMap=new HashMap<>();

    /**
     * 库存是否已经全部清空
     */
    protected static Map<String, Boolean> zeroStock=new ConcurrentHashMap<>();



    /**
     * 数据初始化
     * 初始化数据--》检查初始化是否完成。
     * @param applicationContext
     * @throws BeansException 如果初初始化检查失败，会抛出异常，容器无法正常提供服务
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        initStock();
        isReady();
    }




    /**
     * 获取奖品的下标
     * 通过二分搜索获取随机值在概率数组中的索引
     * @param randomNum
     * @return
     */
    public static int getPrizeIndex(int randomNum, String activityId)
    {
        int i = Collections.binarySearch(prizeProbability.get(activityId),randomNum);
        if(i<0)
        {
            return -i-1;
        }
        else
        {
            return i;
        }
    }

    /**
     * 判断是否超库存
     * 从redis中获取库存值，如果超过了库存返回false，不然返回true
     * @param prizeIndex
     * @return
     */
    public boolean isNotOver(int prizeIndex, String activityId)
    {
        Long stock = Long.valueOf(stringRedisTemplate.opsForHash().get(LOTTERY_KEY.get(activityId), prize.get(activityId).get(prizeIndex))+"");
        if(stocks.get(activityId).get(prizeIndex)==-1)
        {
            return true;
        }
        else if(stock<=stocks.get(activityId).get(prizeIndex))
        {
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * 获取兜底奖励
     * 从thankMin到sum之间随机一个数字，即选择一个兜底奖励
     * @return
     */
    public int thank(String activityId){
        /*兜底奖励*/
        int prizeIndex=getPrizeIndex(RandomUtil.randomInt(thankMin.get(activityId),sum.get(activityId)),activityId);
        addAwardRecord(prize.get(activityId).get(prizeIndex),activityId);
        return prizeIndex;
    }

    /**
     * 默认抽奖方法
     * 先进行校验，validate方法通过抛出异常来截停抽奖。
     * 抽奖逻辑：根据随机值选择一个奖品id--》判断是否超库存--》redis对应的奖品进行increment--》再次判断是否超过库存
     * @return
     */
    public LotteryConfig lottery(String activityId) {
        validate(activityId);

        /*判断当前活动是否有库存，如果没有库存则直接走兜底*/
        if (zeroStock.get(activityId))
        {
            /*兜底奖励*/
            int prizeIndex=thank(activityId);
            return lotteryConfigMap.get(prize.get(activityId).get(prizeIndex));
        }

        /*抽奖，获取一个奖品id的下标*/
        int prizeIndex = getPrizeIndex(RandomUtil.randomInt(sum.get(activityId)),activityId);

        /*如果为无限制库存奖品，直接增加中奖记录并返回*/
        if(lotteryConfigMap.get(prize.get(activityId).get(prizeIndex)).getStock()==-1)
        {
            /*插入中奖记录*/
            addAwardRecord(prize.get(activityId).get(prizeIndex),activityId);
            return lotteryConfigMap.get(prize.get(activityId).get(prizeIndex));
        }

        /*判断是否超过库存限制*/
        if(isNotOver(prizeIndex,activityId))
        {
            /*发号，号码为当前的库存值*/
            Long num = stringRedisTemplate.opsForHash().increment(LOTTERY_KEY.get(activityId), prize.get(activityId).get(prizeIndex), 1);

            /*再次判断是否超过库存*/
            if(isNotOver(prizeIndex,activityId))
            {
                /*插入中奖记录*/
                addAwardRecord(prize.get(activityId).get(prizeIndex),activityId);
            }
            else {
                /*兜底奖励*/
                prizeIndex=thank(activityId);
            }
        }
        else{
            /*兜底奖励*/
            prizeIndex=thank(activityId);
        }
        return lotteryConfigMap.get(prize.get(activityId).get(prizeIndex));
    }

    /**
     * 检查初始化设置是否完成
     * @return
     */
    public boolean isReady()
    {
        for (String id : activityIdList) {
            Assert.notEmpty(stocks.get(id),"stocks库存列表未初始化");
            Assert.notEmpty(prizeProbability.get(id),"prizeProbability奖品概率配置未初始化");
            Assert.notEmpty(prize.get(id),"prize奖品id未初始化");
            Assert.isFalse(thankMin.get(id)==0,"thankMin兜底奖励最小随机值未初始化");
            Assert.isFalse(sum.get(id)==0,"sum最大随机值未初始化");
            Assert.isFalse(LOTTERY_KEY.get(id).equals("DEFAULT_LOTTERY_NUM"),"LOTTERY_KEY库存KEY值未设置");
            Long size = stringRedisTemplate.opsForHash().size(LOTTERY_KEY.get(id));
            Assert.isFalse(size==0L,"REDIS中的库存未设置");
            Assert.isFalse(prizeProbability.get(id).size()<prize.get(id).size(),"概率数组和奖品id数组长度不一致");
        }
        Assert.notEmpty(lotteryConfigMap,"lotteryConfigMap奖品配置未初始化");
        return true;
    }


    /**
     * 校验是否有资格抽奖
     * @return
     */
    public abstract boolean validate(String activityId);

    /**
     * 初始化库存,注意兜底奖励必须在最后
     */
    public abstract void initStock();

    /**
     * 增加中奖记录
     * @param activityId
     * @param id
     * @return
     */
    public abstract String addAwardRecord(String id, String activityId);

    /**
     * 定时同步0库存活动标识
     */
    @Scheduled(cron = "0 0/1 * * * ? *")
    public void checkStock()
    {
        for (Map.Entry<String, Boolean> entry : zeroStock.entrySet()) {
            int prizeCount=0;
            int zeroCount=0;
            for (Map.Entry<String, LotteryConfig> config : lotteryConfigMap.entrySet()) {
                if(config.getValue().getActivityId().equals(entry.getKey())&&config.getValue().getStock()>0)
                {
                    prizeCount++;
                    Long stock = Long.valueOf(stringRedisTemplate.opsForHash().get(LOTTERY_KEY.get(entry.getKey()), config.getValue().getId())+"");
                    if(stock-config.getValue().getStock()>=0)
                    {
                        //当前活动的当前奖品没有库存了
                        zeroCount++;
                    }
                }
            }
            if(zeroCount==prizeCount)
            {
                //0库存奖品数等于奖品数则说明该活动没有库存了
                zeroStock.put(entry.getKey(),true);
            }
            else{
                //0库存奖品数等于奖品数则说明该活动没有库存了
                zeroStock.put(entry.getKey(),false);
            }
        }

    }
}
